package org.dreamwork.jasmine2.editor;

import org.dreamwork.config.ApplicationConfigParser;
import org.dreamwork.jasmine2.engine.JasmineEngine;
import org.dreamwork.misc.Base64;
import org.dreamwork.util.FileInfo;
import org.dreamwork.util.StringUtil;
import org.dreamwork.util.VirtualFile;

import javax.imageio.ImageIO;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpSession;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.text.Collator;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Pattern;

/**
 * Created with IntelliJ IDEA.
 * User: seth.yang
 * Date: 14-3-5
 * Time: 下午7:14
 */
public class FSResourceFetcher implements IResourceFetcher {
    private ServletContext application;
    protected static final String ENC = "UTF-8";

    protected static File basedir;
//    private static boolean withMapping = false;
    protected static final Object locker = new Object ();
    protected static final FileFilter dirFilter = new FileFilter () {
        @Override
        public boolean accept (File dir) {
            return dir.isDirectory ();
        }
    };
    protected static final FileFilter imageFilter = new FileFilter () {
        private final Pattern p = Pattern.compile ("^.*?\\.(jpg|jpeg|png|gif)$", Pattern.CASE_INSENSITIVE);
        public boolean accept (File file) {
            return !file.isDirectory () && p.matcher (file.getName ()).matches ();
        }
    };

    public static final String MAPPING = "user-upload";

    public FSResourceFetcher (ServletContext application) {
        this.application = application;
        synchronized (locker) {
            if (basedir == null) {
                createBasedir ();
            }
        }
    }

    /**
     * 根据资源路径，返回该路径下的所有可用资源列表
     *
     * @param contextPath    Web 的映射目录
     * @param path       资源路径
     * @param parameters 额外参数 该参数可能为空
     * @return 返回的资源列表，代理 Servlet 将会把这个返回列表编码成 json 格式输出到客户端
     */
    @Override
    public List<Map<String, Object>> getResourceList (String contextPath, String path, Map<String, Object> parameters) throws IOException {
        SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss");
        if (basedir == null || !basedir.exists ()) return null;

        File target = new File (basedir, path);
        if (!target.exists ()) return null;

        List<Map<String, Object>> list = new ArrayList<Map<String, Object>> ();
        File[] files = target.listFiles (dirFilter);
        if (files != null) for (File dir : files) {
            Map<String, Object> map = new HashMap<String, Object> ();
            map.put ("name", base64Decode (dir.getName ()));
            map.put ("type", "folder");
            map.put ("src", dir.getName ());
            list.add (map);
        }
        files = target.listFiles (imageFilter);
        try {
            String prefix = "/" + contextPath + '/' + MAPPING + '/' + path;
/*
            String[] a = path.split ("/");
            StringBuilder builder = new StringBuilder ();
            for (String part : a) {
                if (builder.length () > 0) builder.append ('/');
                builder.append (URLEncoder.encode (part, "utf-8"));
            }
            prefix += builder;
*/
            if (files != null) for (File file : files) {
                String name = file.getName ();
                String ext = FileInfo.getExtension (name);
                name = FileInfo.getFileNameWithoutExtension (name);
//                name = new String (Base64.decode (name.getBytes (ENC)), ENC);
                name = base64Decode (name);
                name = name + '.' + ext;
                BufferedImage image = ImageIO.read (file);
                int width = image.getWidth (), height = image.getHeight ();

                Map<String, Object> map = new HashMap<String, Object> ();
                map.put ("name", name);
                map.put ("type", FileInfo.getMimeType (name).getName ());
                map.put ("lastModify", sdf.format (new Date (file.lastModified ())));
                map.put ("width", width);
                map.put ("height", height);
                map.put ("src", prefix + '/' + file.getName ());
                list.add (map);
            }

            return sort (list);
        } catch (Exception ex) {
            throw new RuntimeException (ex);
        }
//        return list;
    }

    /**
     * 根据 resourceName 获取单个资源的内容，以 java.io.InputStream 形式返回
     *
     * @param resourceName 资源名称
     * @return 资源内容
     */
    @Override
    public InputStream getResource (String resourceName) {
        if (basedir == null || !basedir.exists ()) return null;

        File target = new File (basedir, resourceName);
        if (!target.exists ()) return null;

        try {
            return new FileInputStream (target);
        } catch (Exception ex) {
            throw new RuntimeException (ex);
        }
    }

    /**
     * 对于私有资源，处理 Servlet 将统一以 private 作为资源路径的根目录下，实现类应该有自己的算法，将 private
     * 转化为系统当前用户的私有目录
     *
     * @param session 存放系统当前用户的 HttpSession 容器
     * @return 转换后的 private 所对应的系统当前用户的私有目录名称
     */
    @Override
    public String getPrivateFolderName (HttpSession session) {
        return "private";
    }

    /**
     * 对于公共资源，处理 Servlet 将统一以 public 作为资源路径的根目录. 实现类应该有自己的算法，将 public
     * 转换为系统当前用户的公共目录
     *
     * @return 转换以后的 public 所对应的系统当前用户的公共目录名称
     */
    @Override
    public String getPublicFolderName () {
        return "public";
    }

    /**
     * 是否支持所有用户都有权限访问的公共目录.
     * 处理 Servlet 使用 public 来标注公共目录，实现类必须能够理解这个 public 字面量，以便在存取资源时能够做出
     * 正确的响应
     *
     * @return true - 支持公共目录，否则返回 false
     */
    @Override
    public boolean isPublicFolderSupported (HttpSession session) {
        return false;
    }

    /**
     * 保存资源.
     *
     * @param file 用户上传的资源虚拟文件
     * @param path 以 public 或 private 开头的资源存放目录映射
     */
    @Override
    public String save (VirtualFile file, String path) {
        // mapping = user-uploads
        // path = public/a/b/c
//        if (path.charAt (0) == '/') path = path.substring (1);
        try {
            path = base64EncodePath (path, false);
            File target = new File (basedir, path);
            if (!target.exists () && !target.mkdirs ()) {
                throw new RuntimeException ("Can't create dir: " + target);
            }

            String ext = FileInfo.getExtension (file.getName ());
            String name = FileInfo.getFileNameWithoutExtension (file.getName ());
            name = base64Encode (name);
            target = new File (target, name + '.' + ext);
            file.saveAs (target.getCanonicalPath ());

            return name + '.' + ext;
        } catch (Exception ex) {
            throw new RuntimeException (ex);
        }
    }

    @Override
    public String save (VirtualFile file, String path, Map<String, Object> params) {
        return save (file, path);
    }

    /**
     * 创建目录
     *
     * @param folder 目录全路径，相对于 basedir
     * @return 成功 - true, 否则返回 false
     */
    @Override
    public String createFolder (String folder) {
        try {
            String path = base64EncodePath (folder, false);
            File dir = new File (basedir, path);
            if (dir.exists () || dir.mkdirs ()) {
                int pos = path.lastIndexOf ('/');
                if (pos > -1)
                    return path.substring (pos + 1);
                return path;
            }
            throw new RuntimeException ("Can't create folder: " + folder);
        } catch (IOException ex) {
            throw new RuntimeException (ex);
        }
    }

    @Override
    public int getSelectionMode () {
        return SELECTION_MODE_SINGLE;
    }

    @Override
    public boolean canCreateFolder (HttpSession session) {
        return false;
    }

    @Override
    public boolean canRemoveItem (HttpSession session) {
        return false;
    }

    @Override
    public void remove (String... items) {

    }

    @Override
    public long getLastModified (String resourceName) {
        if (basedir == null || !basedir.exists ()) return -1;

        File target = new File (basedir, resourceName);
        if (!target.exists ()) return -1;

        return target.lastModified ();
    }

    private void createBasedir () {
        try {
            URL url = application.getResource ("/WEB-INF/mapping.xml");
            if (url == null) {
                ClassLoader loader = getClass ().getClassLoader ();
                url = loader.getResource ("mapping.xml");
                if (url == null)
                    url = loader.getResource ("/MEAT-INF/mapping.xml");
            }

            if (url != null) {
                ApplicationConfigParser parser = new ApplicationConfigParser (url);
                parser.addParserClass ("mappings", MappingConfigParser.class);
                parser.parse ();
                @SuppressWarnings ("unchecked")
                Map<String, String> urls = (Map<String, String>) parser.getValue ("mappings");
                if (urls == null || urls.isEmpty ()) {
                    createDefaultBasedir ();
                } else {
                    String folderName = urls.values ().iterator ().next ();
                    basedir = new File (new URL (folderName).toURI ());
                }
            } else {
                createDefaultBasedir ();
            }

            if (!basedir.exists () && !basedir.mkdirs ()) {
                throw new Exception ("Can't not create folder:" + basedir.getAbsolutePath ());
            }
        } catch (Exception ex) {
            throw new RuntimeException (ex);
        }
    }

    private void createDefaultBasedir () {
        File file = (File) application.getAttribute (JasmineEngine.J2EE_TEMP_DIR);
        basedir = new File (file, MAPPING);
    }

    protected String base64Encode (String text) throws UnsupportedEncodingException {
        String encoded = new String (Base64.encode (text.getBytes (ENC)), ENC);
        return encoded.replace ('+', '-').replace ('/', '$');
    }

    protected String base64Decode (String text) throws IOException {
        text = text.replace ('$', '/').replace ('-', '+');
        return new String (Base64.decode (text), ENC);
    }

    protected String base64EncodePath (String path, boolean isFile) throws IOException {
        if (StringUtil.isEmpty (path)) return path;
        if (path.charAt (0) == '/') path = path.substring (1);
        String[] a = path.split ("/");

        StringBuilder builder = new StringBuilder (a [0]);
        int length = isFile ? a.length - 1 : a.length;
        for (int i = 1; i < length; i ++) {
            builder.append ('/').append (base64Encode (a [i]));
        }

        if (isFile) {
            String name = a [a.length - 1];
            String ext = FileInfo.getExtension (name);
            String fileName = FileInfo.getFileNameWithoutExtension (name);

            builder.append ('/').append (base64Encode (fileName)).append ('.').append (ext);
        }
        return builder.toString ();
    }

    private List<Map<String, Object>> sort (List<Map<String, Object>> list) {
        List<Map<String, Object>> t0 = new ArrayList<Map<String, Object>> ();
        List<Map<String, Object>> t1 = new ArrayList<Map<String, Object>> ();
        List<Map<String, Object>> t2 = new ArrayList<Map<String, Object>> ();
        for (Map<String, Object> m : list) {
            String type = (String) m.get ("type");
            if ("folder".equals (type)) t1.add (m);
            else t2.add (m);
        }
        Collections.sort (t1, sorter);
        Collections.sort (t2, sorter);
        t0.addAll (t1);
        t0.addAll (t2);
        return t0;
    }

    private final static Comparator<Map<String, Object>> sorter = new Comparator<Map<String, Object>> () {
        Collator c = Collator.getInstance (Locale.CHINESE);
        @Override
        public int compare (Map<String, Object> m1, Map<String, Object> m2) {
            String n1 = (String) m1.get ("name"), n2 = (String) m2.get ("name");
            return c.compare(n1, n2);
        }
    };
}